﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

namespace NQueries.Utils
{
    /// <summary>
    /// Contains various extension methods for types.
    /// </summary>
    public static class TypeExtensions
    {
        /// <summary>
        /// Gets the <see cref="PropertyInfo"/> of the id property if there is any.
        /// Throws an exception if no id property is present.
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static PropertyInfo GetPrimaryKeyPropertyInfo(this Type entityType)
        {
            Check.IsNotNull(entityType, "entityType");
            PropertyInfo propertyInfo = entityType.GetProperties().SingleOrDefault(x => x.IsPrimaryKey());
            if (propertyInfo == null)
                throw new NQueriesException("entity <{0}> does not have a key value".FormatWith(entityType));
            return propertyInfo;
        }

        /// <summary>
        /// Gets a value indicating if this type has a key property and thus might be an entity from the database.
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "HasId")]
        public static bool HasIdProperty(this Type entityType)
        {
            Check.IsNotNull(entityType, "entityType");
            return entityType.GetProperties().FirstOrDefault(propertyInfo => propertyInfo.IsPrimaryKey()) != null;
        }

        /// <summary>
        /// Checks is the type is an generic collection of entities.
        /// </summary>
        /// <param name="collectionType"></param>
        /// <returns></returns>
        public static bool IsCollectionWithIds(this Type collectionType)
        {
            Check.IsNotNull(collectionType, "collectionType");

            if (typeof(IEnumerable).IsAssignableFrom(collectionType))
                if (collectionType.GetGenericArguments().Count() == 1)
                    return collectionType.GetGenericArguments().First().HasIdProperty();

            return false;
        }

        ///// <summary>
        ///// Gets the id of the given entity.
        ///// </summary>
        ///// <param name="entityType"></param>
        ///// <param name="entity"></param>
        ///// <returns></returns>
        //public static object GetId(this Type entityType, object entity)
        //{
        //    Check.IsNotNull(entityType, "entityType");
        //    Check.IsNotNull(entity, "entity");
        //    Check.IsOfType(entity, entityType, "entity");

        //    return entityType.GetPrimaryKeyPropertyInfo().GetValue(entity, null);
        //}

        /// <summary>
        /// Checks if a type is a entity.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static bool IsEntity(this Type type) { return type.GetAttributeOf<EntityAttribute>() != null; }

        /// <summary>
        /// Checks if the object is an entity.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool IsEntity(this object value)
        {
            Check.IsNotNull(value, "value");

            return value.GetType().IsEntity();
        }

        /// <summary>
        /// Checks if the enumeration contains entities.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsEnumerableWithEntities(this Type type)
        {
            Check.IsNotNull(type, "type");

            if (type.IsArray)
                return type.GetElementType().IsEntity();

            if (type.IsGenericType)
                if (typeof(IEnumerable).IsAssignableFrom(type))
                    if (type.GetGenericArguments().First().IsEntity())
                        return true;

            return false;
            //return (type.IsArray && type.GetElementType().IsEntity()
            //    || type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type) && type.GetGenericArguments().First().IsEntity());
        }

        /// <summary>
        /// Checks if the enumeration contains entities.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <returns></returns>
        public static bool IsEnumerableWithEntities<T>(this IEnumerable<T> values)
        {
            Check.IsNotNull(values, "values");

            return IsEntity(typeof(T));
        }
    }
}